package network;
/**
 * @author Gianni Gambetti
 * Messages are sent back to CORE.Main.msgReceive(String)
 * They must be handled by that method
 */
import java.io.IOException;
import java.io.PrintStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;

import transfer.FileFragment;
import transfer.FileTransfer;

import core.Phrox;
import core.Util;
import encryption.EncryptionPlugin;

public class Old_Client{

	private FileTransfer ft = null;
	private InetAddress host;
	private int port;
	private Connection con;
//	private long lastSent = 0;//our job to make sure that the server doesn't time out on us.

	public Old_Client(InetAddress host, int port){
		this(host,port,null);
	}

	public Old_Client(InetAddress host, int port, EncryptionPlugin en){
		this.host = host;
		this.port = port;
		con = new Connection(null,en,false);
	}



	/**
	 * Change host, this only works if the previous host was 'null'
	 * @param h the new host
	 * @return returns true if the new host could be set
	 */
	public boolean setHost(InetAddress h){
		if(host == null){
			this.host = h;
			return true;
		}
		return false;
	}

	/**
	 * 
	 * @param c connection to connect.
	 * @return returns true if the connection connected, false if there was a problem
	 * @throws DisconnectionException 
	 * @throws DisconnectionException 
	 * @throws IOException 
	 * @throws IOException 
	 */
	public boolean connect() throws DisconnectionException, IOException {
		con.connectionStatus = 0;
		//core.Phrox.outln("CLIENT: "+NetworkVars.name);
		try {
			core.Phrox.outln("Connecting to: "+host+":"+port);
			System.out.println("C: "+host.getHostAddress()+":"+port);
			con.open();
			con.socket= new Socket(host,port);
			con.socket.setKeepAlive(true);//so that our TCP/IP connection doesn't get dropped after a while
			con.stdOut = new PrintStream(con.socket.getOutputStream(),true);
			con.msgIn = new MessageReader(con.socket.getInputStream());

			con.localDisconnect = true; //assume we disconnect

		} catch (UnknownHostException e) {
			core.Phrox.outln("Couldn't connect to server.");
		} catch (IOException e) {
			core.Phrox.outln("Couldn't connect to server.");
		} finally {
			//if in or out are null, we arn't connected.
			if(con.stdOut==null || con.msgIn==null){
				System.err.println("NO Connection, aborting attempt.");
				con.connectionStatus = -1;
				throw new DisconnectionException();
			}
		}

		System.out.println("Connection established");

		Message in=new Message(Message.CONNECTION_MSG,NetworkingProtocol.CONNECT_REQ)
		, out=new Message(Message.CONNECTION_MSG,"");
			while(out.getText() != null && !out.getText().equals("CONNECTED")){


				in = read();
				if(in.getText().equals("ERROR")){
					//error
					con.connectionStatus = -1;
					throw new DisconnectionException();
				}
				if(NetworkVars.showData){
					System.out.println(in.getText());
				}
				out.setText( con.netProt.protocol(in.getText()) );
				write(out);

			}
		if(out.getText() == null){
			con.connectionStatus = -1;
			throw new DisconnectionException();
		}
		System.out.println("Connected");
		con.connectionStatus = 1;
		con.aliveNow();
		return true;
	}

	public void run(){

		try {
			connect();
		} catch (Exception e) {
			e.printStackTrace();
			return;
		}
		{
			Message m = new Message(Message.CHANGE_NAME,NetworkVars.id);
			send(m);
			System.out.println("Changed Name");
		}
		
		//initialize lag showing thing.
		Phrox.hasLag( (byte)0 );
		
		while(!core.Phrox.exit && !con.flagDisconnect){


			try {
				Thread.sleep(NetworkVars.defSleep);
			} catch (InterruptedException e1) {}

			try {

				Message m = null;
				if(con.msgIn.ready()){

					con.lastAlive = System.currentTimeMillis();

					String in = con.msgIn.readMessage();
					m = Message.parseMessage(in);

					if(con.encP != null){
						m.setText( con.encP.decrypt(m.getText()));
					}
					if(NetworkVars.showData){
						System.out.println("IN: "+m.getText());
					}

					String tmp = null;
					Message send = null;

					switch(m.getType()){

					case Message.TEXT_MSG://msgType.TEXT_MSG
						core.Phrox.outln(m.getText());
						break;


					case Message.PINGS://Message.PINGC
						//for a ping the contents are the time it was sent, so we just send it back.
						// so that the computer on the other end knows the time lapse.

						long start = Long.parseLong(m.getText());
						long end = System.currentTimeMillis();
						double lapse = (end - start)/1.0;
						tmp = "Lag from server: "+lapse+"ms";
						System.out.println(tmp);
						Phrox.outln(tmp);


						break;

					case Message.PINGR://msgType.PINGS
						send = m;
						System.out.println("Pinged");
						break;

					case Message.FILE_FRAGMENT://Message.FILE_FRAGMENT:
						//handle this later
						if(ft == null){
							ft = new FileTransfer("",false);
						}
						FileFragment ffrag = new FileFragment(Util.getBytes(m.getText()));
						ft.saveFrag(ffrag);
						break;

					case Message.CONNECTION_MSG://Message.CONNECTION_MSG:
						con.flagDisconnect=true;
						con.localDisconnect=false;
						break;

					case Message.KEEP_ALIVE:
						con.aliveNow();
						System.out.println("Kept Alive");
						break;

					default:
					case Message.CHANGE_NAME://Message.CHANGE_NAME:
					case Message.REQUEST_MSG://Message.REQUEST_MSG:
					case Message.UNDEFINED_TYPE://Message.UNDEFINED_TYPE:
						//these messages arn't handled by the server
						System.out.println("Unknown Msg Type:"+m.getType()+"-"+m.getText()+"");
						break;
					}

					if(send != null){
						write(send);
					}
				}

				if( core.Util.timeLapsed(System.currentTimeMillis(), con.lastAlive, NetworkVars.connection_Timeout )){//see if it's been a minute
					//assume they disconnected, because they timed out.
					con.flagDisconnect = true;
					if(NetworkVars.debug){
						System.out.println("Disconnecting due to timeout");
					}
					Phrox.outln("Disconnected from server due to timeout.");
					Phrox.disconnect = true;
					dropConnection();
					break;
				}

				int numSend = con.numSend();
				if(numSend > 0){//if we need to send this connection messages...
					//System.out.println("To send: "+numSend);
					for(Message msgSend : con.getMsgList()){
						if(msgSend != null){
							write(msgSend);
							msgSend = null;//remove the sent message from the list
						}
					}
					con.lastSent = System.currentTimeMillis();
				}

				if( core.Util.timeLapsed(System.currentTimeMillis(), con.lastSent, NetworkVars.lag_Timeout)){
					//make sure the connection is kept alive
					send(new Message(Message.KEEP_ALIVE,""));
					System.out.println("Keep Alive Msg Sent");

					if( core.Util.timeLapsed(con.lastAlive, System.currentTimeMillis(), NetworkVars.lag_Timeout+1000)){
						//since the last message we got, if it's been more then our lag threshold.
						//experiencing lag, problems?
						System.out.println("LAG? "+con.lastAlive+"="+System.currentTimeMillis());
						//core.Phrox.outln("You may be experiencing lag from the server.");
						//don't send the user a message, it'll just spam them if we don't connect.
						Phrox.hasLag((byte)1);
					}else{
						Phrox.hasLag((byte)0);
					}

				}




			} catch (IOException e) {
				e.printStackTrace();
				//socket exception
				break;
			} catch (MessageFormatException e) {
				e.printStackTrace();
			} catch (DisconnectionException e) {
				e.printStackTrace();
			}finally {

			}



		}
		//check to see if we're still connected, then disconnect.

		try {
			Phrox.disconnect = true;
			disconnect(con.localDisconnect);
		} catch (Exception e) {/*Ignore this, as we are just making sure that we disconnected*/}



	}

	public void send(Message m){
		con.addMsg(m);
	}

	public void sendText(String str){
		Message m = new Message(Message.TEXT_MSG, NetworkVars.id+": "+str);
		send(m);
	}

	//called by outside, to ask for a disconnect.
	public void disconnect(){
		Phrox.disconnect=true;
		con.flagDisconnect = true;
		con.localDisconnect = true;
	}


	/**
	 * @return returns whether or not the disconnect was succesful
	 * @param b whether or not we started the disconnect.
	 * @throws IOException 
	 * @throws DisconnectionException if the connection has already been disconnected, or is in the process of being disconnected.
	 */
	private boolean disconnect(boolean b) throws IOException, DisconnectionException {
		if(con.connectionStatus != 1){//if were not connected, (we're disconnecting, or unconnected), throw exception
			throw new DisconnectionException("Already disconnected");
		}
		con.connectionStatus = 0;


			Message in=new Message(Message.CONNECTION_MSG, NetworkingProtocol.DISCONNECT)
			, out=new Message(Message.CONNECTION_MSG,NetworkingProtocol.DISCONNECT);

			if(b){
				//client started it
				Message leave = new Message(Message.TEXT_MSG, NetworkVars.id+" is leaving...");//this message can be blank.
				write(leave);//send a message saying we're leaving.
			}else{
				out.setText( con.netProt.protocol(in.getText()) );
			}
			while(!out.getText().equals( NetworkingProtocol.END )){
				write(out);
				do{
					in = read();//drop all messages that arn't connection msgs.
				}while(in.getType() != Message.CONNECTION_MSG);

				out.setText( con.netProt.protocol(in.getText()) );
			}
			write(out);
			if(b){//client needs to get the "goodbye" message from server
				//it will ensure that the protocol is ready for the next connection
				con.netProt.protocol(NetworkingProtocol.DISCONNECT);
			}

		core.Phrox.outln("Disconnected from "+host+":"+port);
		con.connectionStatus = -1;
		con.close();
		return true;
		//throw new DisconnectionException("Disconnected succesfully");

	}

	/**
	 * This method closes the connection, without using protocol. used only if the connection times out or
	 * can not be disconnected from properly.
	 * @return
	 */
	private boolean dropConnection() throws DisconnectionException {
		if(con.connectionStatus != 1){//if were not connected, (we're disconnecting, or unconnected), throw exception
			throw new DisconnectionException("Already disconnected");
		}
		con.connectionStatus = 0;
		con.netProt.reset();
		//gotta reset the connection protocol
		core.Phrox.outln("DISCONNECTED");
		con.connectionStatus = -1;
		con.close();
		return true;
	}


	public Message read() throws DisconnectionException, IOException{
		if(con.connectionStatus == -1){
			throw new DisconnectionException();
		}
		Message ret = null;
		while(!con.msgIn.ready());
		String in = con.msgIn.readMessage();
		try {
			ret = Message.parseMessage(in);
		} catch (MessageFormatException e) {}
		if(NetworkVars.showData){
			System.out.println("IN: "+ret.getText());
		}
		if(con.encP != null){
			ret.setText( con.encP.decrypt(ret.getText()) );
		}
		if(NetworkVars.showData){
			System.out.println("IN: "+ret.getText());
		}

		return ret;
	}


	public synchronized void write(Message send) throws DisconnectionException{
		Message m = (Message)send.clone();
		if(con.connectionStatus == -1){
			throw new DisconnectionException();
		}
		if(NetworkVars.showData){
			System.out.println("OUT: "+m.getText());
		}
		if(con.encP != null){//encrypt only the contents of the msg.
			m.setText( con.encP.encrypt(m.getText()));
		}
		if(NetworkVars.showData){
			System.out.println("OUT: "+m.getText());
		}
		con.stdOut.print(""+Message.convertMessage(m)+""+(char)Message.DELIMITER);

	}



	public byte getConStat(){
		return con.connectionStatus;
	}

	public InetAddress getHost(){
		return this.host;
	}

	public String clientCommands(String str) throws NotACommandException {
		String ret = "";
		System.out.println("command:"+str);
		if(str.length() > 3){
			if(str.charAt(0) == '/'){
				if(str.equalsIgnoreCase("/cls")){
					Phrox.gui_ClearText();

				}else if(str.equalsIgnoreCase("/ping")){
					long start = System.currentTimeMillis();
					Message m = new Message(Message.PINGS,""+start);
					send(m);

				}else if(str.toLowerCase().contains("/name")){
					//change name
					String name = str.substring(5).trim();
					Message m = new Message(Message.CHANGE_NAME,""+name);
					send(m);
					NetworkVars.id=name;
					Phrox.gui_changeName();

				}else if(str.equalsIgnoreCase("/help")){
					ret ="Commands for the client are:\n" +
					"/members\t\t\tprints a list of all members\n" +
					"/ping\t\t\tcheck latancy to server\n" +
					"/name\t[alpha]\t\tchange your name to 'alpha'\n" +
					"/cls\t\t\tclears the text area\n" +
					"/leave\t\t\tdisconnects from server";

				}else if(str.equalsIgnoreCase("/leave")){
					clientCommands("/cls");
					//disconnect from server
					ret = "Disconnecting from server located at:"+this.host.getHostAddress();
					this.disconnect();
				}else if(str.equalsIgnoreCase("/detect")){
					Phrox.outln("Detecting Hosts, could take a while");//don't really want to print straight out
					ret = Phrox.getBroadcastedHosts();

				}else{
					//it wasn't a command.
					ret = null;
				}
			}
		}
		if(ret == null){
			throw new NotACommandException(str);
		}
		return ret;
	}

	public void prepConnect(){
		con.flagDisconnect = false;
		con.localDisconnect = false;
		con.netProt.reset();
	}

}
